// threadtest.cc
//        Simple test case for the threads assignment.
//
//        Create two threads, and have them context switch
//        back and forth between themselves by calling Thread::Yield,
//        to illustratethe inner workings of the thread system.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.


#include "copyright.h"
#include "system.h"
#include "synch.h"

#define NMACHINES 10


// testnum is set in main.cc
int testnum = 1;

int SharedVariable;
int available[NMACHINES];
int workingThreads;

//----------------------------------------------------------------------
// SimpleThread
//         Loop 5 times, yielding the CPU to another ready thread
//        each iteration.
//
//        "which" is simply a number identifying the thread, for debugging
//        purposes.
//----------------------------------------------------------------------

//----------------------------------------------------------------------
// ThreadTest1
//         Set up a ping-pong between two threads, by forking a thread
//        to call SimpleThread, and then calling SimpleThread ourselves.
//----------------------------------------------------------------------

//----------------------------------------------------------------------
// ThreadTest
//         Invoke a test routine.
//----------------------------------------------------------------------

#if defined(CHANGED) && defined(THREADS)

// --------------------- SEMAPHORES -----------------------
#if defined(HW1_SEMAPHORES)

Semaphore *sharedVarSemaphore = new Semaphore("sharedVarSem", 1);
Semaphore *workingThreadsSemaphore = new Semaphore("workingThreadsSem", 1);

void SimpleThread(int which)
{
    int num, val;
    for(num = 0; num < 5; num++)
    {
        sharedVarSemaphore->P();

        // ~~~ Critical section ~~~
        val = SharedVariable;
        printf("*** thread %d sees value %d\n", which, val);
        SharedVariable = val+1;
        // ~~~ End Critical section ~~~

        sharedVarSemaphore->V();
        currentThread->Yield();
    }

    // Show that this thread has finished its work
    workingThreadsSemaphore->P();
    workingThreads--;
    workingThreadsSemaphore->V();

    // Wait for all threads to finish working
    while(workingThreads > 0)
        currentThread->Yield();

    // Report the final value
    val = SharedVariable;
    printf("Thread %d sees final value %d\n", which, val);
}

// --------------------- LOCKS -----------------------
#elif defined(HW1_LOCKS)

Lock *sharedVarLock = new Lock("sharedVarLock");
Lock *workingThreadsLock = new Lock("workingThreadsLock");

void SimpleThread(int which)
{
    //printf("Locks Version");
    int num, val;
    for(num = 0; num < 5; num++)
    {
        // Attempt to acquire the lock
        sharedVarLock->Acquire();

        // ~~~ Critical section ~~~
        val = SharedVariable; // Peak at the value
        printf("*** thread %d sees value %d\n", which, val);
        SharedVariable = val+1; // Increment the value seen
        // ~~~ End Critical section ~~~

        sharedVarLock->Release();
        currentThread->Yield();
    }

    // Show that this thread has finished its work
    workingThreadsLock->Acquire();
    workingThreads--;
    workingThreadsLock->Release();

    // Wait for all threads to finish working
    while(workingThreads > 0)
        currentThread->Yield();

    // Report the final value
    val = SharedVariable;
    printf("Thread %d sees final value %d\n", which, val);
}

#else

// ----------------- NO SYNCHRONIZATION -----------------------

void SimpleThread(int which)
{
    int num, val;
    for(num = 0; num < 5; num++)
    {
        val = SharedVariable;
        printf("*** thread %d sees value %d\n", which, val);
        currentThread->Yield();
        SharedVariable = val+1;
        currentThread->Yield();
    }
    val = SharedVariable;
    printf("Thread %d sees final value %d\n", which, val);
}

#endif

#if defined(HW1_LAUNDRY_SEM)
// --------------------------- LAUNDROMAT SEMPHORES ------------------

Semaphore *nfree = new Semaphore("nfree", NMACHINES);
Semaphore *machineAssigner = new Semaphore("machineAssigner", 1);
int freeMachines = NMACHINES;



int laundromatAllocate() /* Returns index of available machine. */
{
    nfree->P(); /* wait until a machine is available */
    machineAssigner->P(); // Wait until no one else is currently assigning machines
    freeMachines--;
    for (int i=0; i < NMACHINES; i++)
        if (available [i] != 0)
        {
            // This yield would cause two stations to assign the same number machine if we hadn't locked access
            // It servers no purpose other than for testing and proving that this implementation works
            //currentThread->Yield();

            available[i] = 0;
            machineAssigner->V();
            return i;
        }
    return -1;
}

void laundromatRelease(int machine) /* Release machine */
{
    machineAssigner->P();
    available[machine] = 1;
    freeMachines++;
    nfree->V();
    machineAssigner->V();

}



#elif defined(HW1_LAUNDRY_LC)
// --------------------------- LAUNDROMAT MONITOR ------------------

Lock *checkLock = new Lock("checkLock");
Condition *cd = new Condition("cd");
int freeMachines = NMACHINES;

int laundromatAllocate() /* Returns index of available machine. */
{
    checkLock->Acquire();
    while(freeMachines == 0)
    {
        cd->Wait(checkLock);
    }

    for (int i=0; i < NMACHINES; i++)
        if (available [i] != 0)
        {
            available[i] = 0;
            freeMachines--;
            checkLock->Release();
            return i;
        }
    return -1;
}

void laundromatRelease(int machine) /* Release machine */
{
    checkLock->Acquire();
    available[machine] = 1;
    freeMachines++;
    cd->Signal(checkLock);
    checkLock->Release();
}

#endif

#if defined(HW1_LAUNDRY_SEM) || defined(HW1_LAUNDRY_LC)



void laundromatCustomer(int customerNumber)
{
    for(int i=0; i<6; i++)
    {
        int assignedMachine = laundromatAllocate();
        printf("Station %d assigned machine %d\n", customerNumber, assignedMachine);
        laundromatRelease(assignedMachine);
        printf("Machine %d has been released\n", assignedMachine);
    }
}

void runStationOne(int stationNumber)
{
    //We have 10 machines. Station 1 needs to allocate 7 machines.

    // Station 1 gets 3 machines then Yields
    int assignedMachine1 = laundromatAllocate();    printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine1, freeMachines);
    int assignedMachine2 = laundromatAllocate();    printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine2, freeMachines);
    int assignedMachine3 = laundromatAllocate();     printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine3, freeMachines);
    currentThread->Yield();

    // Station 1 gets 4 machines then Yields
    int assignedMachine4 = laundromatAllocate();     printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine4, freeMachines);
    int assignedMachine5 = laundromatAllocate();     printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine5, freeMachines);
    int assignedMachine6 = laundromatAllocate();     printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine6, freeMachines);
    int assignedMachine7 = laundromatAllocate();     printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine7, freeMachines);
    currentThread->Yield();

    // Station 1 releases 2 machines and Yealds
    laundromatRelease(assignedMachine1);             printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine1, freeMachines);
    laundromatRelease(assignedMachine2);             printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine2, freeMachines);
    laundromatRelease(assignedMachine3);             printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine3, freeMachines);
    currentThread->Yield();
    // At this point Station 2 has enough resaurces to continue allocation

    // Station 1 releases 2 machines and Yealds
    laundromatRelease(assignedMachine4);            printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine4, freeMachines);
    laundromatRelease(assignedMachine5);            printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine5, freeMachines);
    laundromatRelease(assignedMachine6);            printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine6, freeMachines);
    laundromatRelease(assignedMachine7);            printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine7, freeMachines);
    // Station 1 is done
    printf("Station 1 is done!\n");
}

void runStationTwo(int stationNumber)
{
    //We have 10 machines. Station 2 needs to allocate 6 machines.

    // Station 2 gets 2 machines then Yields
    int assignedMachine1 = laundromatAllocate();        printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine1, freeMachines);
    int assignedMachine2 = laundromatAllocate();        printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine2, freeMachines);
    currentThread->Yield();
    // S1: has(2) needs(4)

    // Station 2 will try to get 4 more machines but there is only one left
    // so it will go to sleep after the first allocation and wait for Station 1
    // to release the resaurces
    int assignedMachine3 = laundromatAllocate();        printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine3, freeMachines);
    int assignedMachine4 = laundromatAllocate(); //This is where Station 2 should be sleeping
    printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine4, freeMachines);
    int assignedMachine5 = laundromatAllocate();        printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine5, freeMachines);
    int assignedMachine6 = laundromatAllocate();        printf("Station %d assigned machine %d (%d machines left)\n", stationNumber, assignedMachine6, freeMachines);
    currentThread->Yield();

    //Station 2 will release all its machines
    laundromatRelease(assignedMachine1);                printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine1, freeMachines);
    laundromatRelease(assignedMachine2);                printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine2, freeMachines);
    laundromatRelease(assignedMachine3);                printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine3, freeMachines);
    laundromatRelease(assignedMachine4);                printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine4, freeMachines);
    laundromatRelease(assignedMachine5);                printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine5, freeMachines);
    laundromatRelease(assignedMachine6);                printf("Station %d released machine %d (%d machines left)\n", stationNumber, assignedMachine6, freeMachines);
    //Station 2 is done
    printf("Station 2 is done!\n");
}

void laundromatTest()
{
    // Mark all of the machines as available
    for(int i=0; i<NMACHINES; i++)
        available[i] = 1;

    Thread *station1 = new Thread("Laundromat station 1");
    Thread *station2 = new Thread("Laundromat station 2");
    station1->Fork(runStationOne, 1);
    station2->Fork(runStationTwo, 2);
}




#endif



void ThreadTest1(int childThreadNum)
{
    DEBUG('t', "Entering ThreadTest1");

    // Prepare a new thread to be forked
    Thread *t = new Thread("forked thread");
    // 'Fork' the new thread, putting it on the ready queue, about to run SimpleThread
    t->Fork(SimpleThread, childThreadNum);
}


void ThreadTest(int numThreads)
{
    if(numThreads < 0)
    {
        printf("Num threads must be >= 0");
        return;
    }

    // No data races for writing workingThreads because only the parent is running at this time
    workingThreads = numThreads+1;

    // Spawn N child threads
    for(int i = 0; i < numThreads; i++)
    {
        ThreadTest1(i+1); // Pass the child thread's number (i+1)
    }

    // Have the parent thread call SimpleThread() after all the children are spawned
    SimpleThread(0);
}

#else // ---------------------- Unchanged -----------------------

void
SimpleThread(int which)
{
    int num;

    for (num = 0; num < 5; num++) {
        printf("*** thread %d looped %d times\n", which, num);
        currentThread->Yield();
    }
}

//----------------------------------------------------------------------
// ThreadTest1
// 	Set up a ping-pong between two threads, by forking a thread
//	to call SimpleThread, and then calling SimpleThread ourselves.
//----------------------------------------------------------------------

void
ThreadTest1()
{
    DEBUG('t', "Entering ThreadTest1");

    Thread *t = new Thread("forked thread");

    t->Fork(SimpleThread, 1);
    SimpleThread(0);
}

//----------------------------------------------------------------------
// ThreadTest
// 	Invoke a test routine.
//----------------------------------------------------------------------

void
ThreadTest()
{
    switch (testnum) {
    case 1:
        ThreadTest1();
        break;
    default:
        printf("No test specified.\n");
        break;
    }
}

#endif
